/*
 * Copyright (c) 2010, Tangedyn <tangedyn at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.cat;

import java.util.HashMap;
import java.util.Map;

import yawning.mew.MewProperty;
import yawning.mew.ProcEffect;
import yawning.mew.Report;
import yawning.mew.BuffsDebuffs.Buff;
import yawning.mew.Report.Stat;
import yawning.mew.cat.CatModelSimState.CatTargetData;
import yawning.mew.cat.CatSimReport.CatSimStat;
import yawning.mew.cat.CatSimReport.CatSimTargetReport;
import yawning.mew.cat.EventMeleeSwing.MeleeStats;
import yawning.mew.cat.SwipeAction.SwipeStats;
import yawning.mew.sim.Simulation;
import yawning.mew.sim.SimulationEngine;
import yawning.mew.sim.SimulationProc;
import yawning.mew.sim.SimulationTarget;
import yawning.mew.sim.Strategy.IAction;
import yawning.mew.sim.Strategy.IStatus;

public class CatModelSimulation implements Simulation {
	private static final String DEFAULT_STRATEGY_FILE = "yawning/mew/cat/CatModelSimulator.script";
	private static final String VERSION_INFO = "x.x.x";

	// Actions that the user can specify.
	public enum Action implements IAction {
		NONE,

		POUNCE,
		RAVAGE,
		CLAW,
		MANGLE,
		RAKE,
		SHRED,
		RIP,
		BITE,
		ROAR,
		MAIM,
		SWIPE,
		FERALFF,
		BERSERK,

		FERALCHARGE,
		TIGERSFURY,
		SKULLBASH,
		POTION,
		RUNAWAY,

		// Swing timer control.
		AUTOATTACK_START,
		AUTOATTACK_STOP,

		// External damage boosting buffs are manually triggered here.
		HEROISM,
		UNHOLYFRENZY,
		SHATTERINGTHROW;
	}

	public static class SimulatorProperty {
		public static final MewProperty ASSUMEEXTERNALBLEED = new MewProperty("Simulator.AssumeExternalBleed", false);		// Mob is always bleeding for purposes of Rend and Tear
		public static final MewProperty ASSUMEEXTERNALFF = new MewProperty("Simulator.AssumeExternalFF", false);				// Mob is always fully debuffed with the MajorArmor debuff
		public static final MewProperty ASSUMEEXTERNALMANGLE = new MewProperty("Simulator.AssumeExternalMangle", false);		// Mob is always fully debuffed with the Bleed Damage debuff
		public static final MewProperty AGGRESSIVEFERALCHARGE = new MewProperty("Simulator.AggressiveFeralCharge", false);	// Script should aggressively Feral Charge
		public static final MewProperty FERALCHARGETRAVELTIME = new MewProperty("Simulator.FeralChargeTravelTime", 0.0);		// Time taken to gain distance + Feral Charge
		public static final MewProperty FRONTALATTACKS = new MewProperty("Simulator.FrontalAttacks", false);					// Frontal attacks only
        public static final MewProperty REBIRTH = new MewProperty("Simulator.Rebirth", false);								// Rebirth during the fight
		public static final MewProperty TRANQUILITY = new MewProperty("Simulator.Tranquility", false);						// Tranquility during the fight
		public static final MewProperty BURNINGTENDON = new MewProperty("Simulator.BurningTendon", false);					// Simulate Burning Tendon burn on Spine of Deathwing
		public static final MewProperty LOGDAMAGE = new MewProperty("Simulator.LogDamage", false);							// Log damage (For graphs)
	}

	@Override
	public Class<?>[] getExportClasses() {
		return new Class<?>[] {
				Action.class,
				CatModelSimState.class
		};
	}

	@Override
	public String getVersionInfo() {
		return VERSION_INFO;
	}

	@Override
	public IStatus initSimulation(SimulationEngine su) {
		CatModelSimState state = new CatModelSimState(su);
		if (!su.hasStrategy()) su.setStrategy(Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_STRATEGY_FILE));
		return state;
	}

	@Override
	public Report getBaseReport() {
		return new CatSimReport();
	}

	@Override
	public Report getReport(IStatus status) {
		if (!(status instanceof CatModelSimState)) {
			throw new RuntimeException("Status not a instance of CatModelSimState");
		}
		CatModelSimState state = (CatModelSimState) status;
		double encounterDuration = state.mSimEngine.getEndTime();

		CatSimReport report = new CatSimReport();

		// Values needed to derive the global stats
		long totalDamage = 0;
		double effectiveEnergySpent = 0.0;
		long whiteDamage = 0;
		long furySwipesDamage = 0;
		long pounceDamage = 0;
		long ravageDamage = 0;
		long clawDamage = 0;
		long mangleDamage = 0;
		long shredDamage = 0;
		long rakeDamage = 0;
		long ripDamage = 0;
		long biteDamage = 0;
		long maimDamage = 0;
		long swipeDamage = 0;
		long fieryClawsDamage = 0;
		long otherDamage = 0;

		int nrWhiteAttacks = 0;
		int nrFurySwipes = 0;

		int nrRavageAttacks = 0;
		int nrRavageFail = 0;
		int nrRavageTF = 0;
//		int nrRavageOoC = 0;

		int nrPounceAttacks = 0;
		int nrPounceFail = 0;
		int nrPounceTicks = 0;
		int nrPounceTicksTF = 0;
//		int nrPounceOoC = 0;

		int nrClawAttacks = 0;
		int nrClawTF = 0;
		int nrClawFail = 0;
		int nrClawOoC = 0;

		int nrMangleAttacks = 0;
		int nrMangleTF = 0;
		int nrMangleFail = 0;
		int nrMangleOoC = 0;

		int nrShredAttacks = 0;
		int nrShredTF = 0;
		int nrShredFail = 0;
		int nrShredOoC = 0;

		int nrRakeAttacks = 0;
		int nrRakeTF = 0;
		int nrRakeFail = 0;
		int nrRakeTicks = 0;
		int nrRakeTicksTF = 0;
		int nrRakeOoC = 0;

		int nrRipTicks = 0;
		int nrRipTicksTF = 0;
		int nrRipOoC = 0;

		int nrRipAttacksDPE = 0;
		int nrRipFailDPE = 0;
		long ripDamageDPE = 0;

		int nrBiteAttacks = 0;
		int nrBiteTF = 0;
		int nrBiteFail = 0;
		int nrBiteExtraEnergy = 0;
		int nrBiteOoC = 0;

		int nrMaimAttacks = 0;
		int nrMaimTF = 0;
		int nrMaimFail = 0;
		int nrMaimOoC = 0;

		int nrRipCPs = 0;
		int nrBiteCPs = 0;
		int nrMaimCPs = 0;

		int pounceEnergy = 0;
		int ravageEnergy = 0;
		int clawEnergy = 0;
		int mangleEnergy = 0;
		int shredEnergy = 0;
		int rakeEnergy = 0;
		int ripEnergy = 0;
		int biteEnergy = 0;
		int maimEnergy = 0;

		boolean didHardCastRavage = false;
		boolean didStampedeRavage = false;

		// Build the per target reports.
		Map<String, CatSimTargetReport> perTargetReports = new HashMap<String,CatSimTargetReport>();
		for (Map.Entry<String,SimulationTarget> entry : state.mTargetMap.entrySet()) {
			String targetName = entry.getKey();
			SimulationTarget target = entry.getValue();

			CatSimTargetReport targetReport = report.new CatSimTargetReport();

			CatTargetData td = (CatTargetData) target.getTargetData();

			SwipeStats swipeStats = state.mSwipeAction.mDamageStats.get(target);
			if (swipeStats == null) {
				swipeStats = state.mSwipeAction.new SwipeStats();
			}

			MeleeStats meleeStats = state.mMeleeSwingEv.mDamageStats.get(target);
			if (meleeStats == null) {
				meleeStats = state.mMeleeSwingEv.new MeleeStats();
			}

			double targetActiveTime = td.mDeathEv.getUpTime();
			double targetBitWTime = (td.mBloodInTheWaterEv.getUpTime() > 0) ? targetActiveTime - td.mBloodInTheWaterEv.getUpTime() : 0;

			long targetTotalDamage = meleeStats.mWhiteDamage + meleeStats.mFurySwipesDamage + td.mClawAction.mClawDamage + td.mMangleAction.mMangleDamage + td.mRakeAction.mRakeDamage + td.mRakeAction.mRakeDotDamage + td.mShredAction.mShredDamage + td.mRipAction.mRipDamage + td.mFerociousBiteAction.mBiteDamage + td.mMaimAction.mMaimDamage + td.mRavageAction.mRavageDamage + td.mPounceAction.mPounceDamage + td.mTier_12_2pcEv.mFieryClawsDmg + td.mProcPhysDamage + td.mProcSpellDamage + swipeStats.mSwipeDamage;
			targetReport.setResult(Stat.DAMAGE_DEALT_PER_SECOND, targetTotalDamage / targetActiveTime);
			targetReport.setResult(CatSimStat.TOTAL_DAMAGE, targetTotalDamage);
			if (td.mBloodInTheWaterEv.mTotalDamageBase > 0 && targetTotalDamage - td.mBloodInTheWaterEv.mTotalDamageBase > 0) {
				targetReport.setResult(CatSimStat.DPS_NORMAL, (double) td.mBloodInTheWaterEv.mTotalDamageBase / (targetActiveTime - targetBitWTime));
				targetReport.setResult(CatSimStat.DPS_BITW, (double) (targetTotalDamage - td.mBloodInTheWaterEv.mTotalDamageBase) / targetBitWTime);
			} else {
				targetReport.setResult(CatSimStat.DPS_NORMAL, 0);
				targetReport.setResult(CatSimStat.DPS_BITW, 0);
				targetReport.mHaveValidBitWDPS = false;
			}

			boolean didDamage = targetTotalDamage > 0;

			// Debuffs
			targetReport.setResult(CatSimStat.FERAL_FF_UPTIME, Math.min(td.mFeralFFAction.getUpTime() / targetActiveTime * 100, 100));
			targetReport.setResult(CatSimStat.SHATTERING_THROW_UPTIME, Math.min(td.mShatteringThrowEv.getUpTime() / targetActiveTime * 100, 100));

			// Interrupts
			targetReport.setResult(CatSimStat.NR_TARGET_CASTS, td.getSimulationTarget().getTargetCastEv().getNrCasts());
			targetReport.setResult(CatSimStat.NR_INTERRUPTED_CASTS, td.getSimulationTarget().getTargetCastEv().getNrInterruptedCasts());
			targetReport.setResult(CatSimStat.NR_SKULL_BASHES, td.mSkullBashAction.mNrSkullBashHit);
			effectiveEnergySpent += td.mSkullBashAction.mNrSkullBashHit * state.mModel.skullBashCost;	// TODO: Should show a percentile breakdown for this

			// White Attacks
			int targetNrWhiteAttacks = meleeStats.mNrWhiteMiss + meleeStats.mNrWhiteDodge + meleeStats.mNrWhiteParry + meleeStats.mNrWhiteGlance + meleeStats.mNrWhiteCrit + meleeStats.mNrWhiteHit;
			targetReport.setResult(CatSimStat.WHITE_ATTACKS, targetNrWhiteAttacks);
			targetReport.setResult(CatSimStat.WHITE_ATTACK_DPS, (double) meleeStats.mWhiteDamage / targetActiveTime);
			targetReport.setResult(CatSimStat.WHITE_ATTACK_UPTIME, 0);
			if (targetNrWhiteAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_MISS, (double) meleeStats.mNrWhiteMiss / targetNrWhiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_DODGE, (double) meleeStats.mNrWhiteDodge / targetNrWhiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_PARRY, (double) meleeStats.mNrWhiteParry / targetNrWhiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_GLANCE, (double) meleeStats.mNrWhiteGlance / targetNrWhiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_CRIT, (double) meleeStats.mNrWhiteCrit / targetNrWhiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_HIT, (double) meleeStats.mNrWhiteHit / targetNrWhiteAttacks * 100);
				targetReport.setResult(CatSimStat.WHITE_ATTACK_AVG_DMG,  (double) meleeStats.mWhiteDamage / targetNrWhiteAttacks);
				targetReport.setResult(CatSimStat.WHITE_ATTACK_SWING_TIMER, 0);
			} else {
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_MISS, 0);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_DODGE, 0);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_PARRY, 0);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_GLANCE, 0);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_CRIT, 0);
				targetReport.setResult(CatSimStat.P_WHITE_ATTACK_HIT, 0);
				targetReport.setResult(CatSimStat.WHITE_ATTACK_AVG_DMG, 0);
			}
			whiteDamage += meleeStats.mWhiteDamage;
			nrWhiteAttacks += targetNrWhiteAttacks;

			// Fury Swipes
			int targetNrFurySwipes = meleeStats.mNrFurySwipesMiss + meleeStats.mNrFurySwipesDodge + meleeStats.mNrFurySwipesParry + meleeStats.mNrFurySwipesCrit + meleeStats.mNrFurySwipesHit;
			targetReport.setResult(CatSimStat.FURY_SWIPES, targetNrFurySwipes);
			targetReport.setResult(CatSimStat.FURY_SWIPES_DPS, (double) meleeStats.mFurySwipesDamage / targetActiveTime);
			if (targetNrFurySwipes > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_MISS, (double) meleeStats.mNrFurySwipesMiss / targetNrFurySwipes * 100);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_DODGE, (double) meleeStats.mNrFurySwipesDodge / targetNrFurySwipes * 100);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_PARRY, (double) meleeStats.mNrFurySwipesParry / targetNrFurySwipes * 100);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_CRIT, (double) meleeStats.mNrFurySwipesCrit / targetNrFurySwipes * 100);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_HIT, (double) meleeStats.mNrFurySwipesHit / targetNrFurySwipes * 100);
				targetReport.setResult(CatSimStat.FURY_SWIPES_AVG_DMG, (double) meleeStats.mFurySwipesDamage / targetNrFurySwipes);
				targetReport.setResult(CatSimStat.FURY_SWIPES_INTERVAL, 0);
			} else {
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_MISS, 0);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_DODGE, 0);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_PARRY, 0);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_CRIT, 0);
				targetReport.setResult(CatSimStat.P_FURY_SWIPES_HIT, 0);
				targetReport.setResult(CatSimStat.FURY_SWIPES_AVG_DMG, 0);
			}
			furySwipesDamage += meleeStats.mFurySwipesDamage;
			nrFurySwipes += targetNrFurySwipes;

			// Pounce
			int targetNrPounceAttacks = td.mPounceAction.mNrPounceMiss + td.mPounceAction.mNrPounceDodge + td.mPounceAction.mNrPounceParry + td.mPounceAction.mNrPounceHit;
			int targetNrPounceTicks = td.mPounceAction.mNrPounceDotCrit + td.mPounceAction.mNrPounceDotHit;
			targetReport.setResult(CatSimStat.POUNCES, targetNrPounceAttacks);
			targetReport.setResult(CatSimStat.POUNCE_TICKS, targetNrPounceTicks);
			targetReport.setResult(CatSimStat.POUNCE_DPS, (double) td.mPounceAction.mPounceDamage / targetActiveTime);
			if (targetNrPounceAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_POUNCE_MISS, (double) td.mPounceAction.mNrPounceMiss / targetNrPounceAttacks * 100);
				targetReport.setResult(CatSimStat.P_POUNCE_DODGE, (double) td.mPounceAction.mNrPounceDodge / targetNrPounceAttacks * 100);
				targetReport.setResult(CatSimStat.P_POUNCE_PARRY, (double) td.mPounceAction.mNrPounceParry / targetNrPounceAttacks * 100);
				targetReport.setResult(CatSimStat.P_POUNCE_HIT, (double) td.mPounceAction.mNrPounceHit / targetNrPounceAttacks * 100);

				targetReport.setResult(CatSimStat.POUNCE_UPTIME, td.mPounceAction.getUpTime() / targetActiveTime * 100);
				if (td.mPounceAction.mPounceDamage > 0) {
					targetReport.setResult(CatSimStat.P_POUNCE_DOT_CRIT, (double) td.mPounceAction.mNrPounceDotCrit / targetNrPounceTicks * 100);
					targetReport.setResult(CatSimStat.P_POUNCE_DOT_HIT, (double) td.mPounceAction.mNrPounceDotHit / targetNrPounceTicks * 100);
					targetReport.setResult(CatSimStat.POUNCE_DOT_AVG_DMG, (double) td.mPounceAction.mPounceDamage / targetNrPounceTicks);
				} else {
					targetReport.setResult(CatSimStat.P_POUNCE_DOT_CRIT, 0);
					targetReport.setResult(CatSimStat.P_POUNCE_DOT_HIT, 0);
					targetReport.setResult(CatSimStat.POUNCE_DOT_AVG_DMG, 0);
				}

				double pPounceFail = (double) (td.mPounceAction.mNrPounceMiss + td.mPounceAction.mNrPounceDodge + td.mPounceAction.mNrPounceParry) / targetNrPounceAttacks;
				effectiveEnergySpent += targetNrPounceAttacks * state.mModel.pounceCost * ((1 - pPounceFail) + 0.2 * pPounceFail);
			} else {
				targetReport.setResult(CatSimStat.P_POUNCE_MISS, 0);
				targetReport.setResult(CatSimStat.P_POUNCE_DODGE, 0);
				targetReport.setResult(CatSimStat.P_POUNCE_PARRY, 0);
				targetReport.setResult(CatSimStat.P_POUNCE_HIT, 0);
				targetReport.setResult(CatSimStat.POUNCE_UPTIME, 0);
				targetReport.setResult(CatSimStat.P_POUNCE_DOT_CRIT, 0);
				targetReport.setResult(CatSimStat.P_POUNCE_DOT_HIT, 0);
				targetReport.setResult(CatSimStat.POUNCE_DOT_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.POUNCE_DPE, 0);
			}
			pounceDamage += td.mPounceAction.mPounceDamage;
			nrPounceAttacks += targetNrPounceAttacks;
			nrPounceFail += td.mPounceAction.mNrPounceDodge + td.mPounceAction.mNrPounceMiss + td.mPounceAction.mNrPounceParry;
			nrPounceTicks += targetNrPounceTicks;
			nrPounceTicksTF += td.mPounceAction.mNrPounceDotTFTicks;
//			nrPounceOoC += td.mPounceAction.mNrPounceOoC;
			pounceEnergy += td.mPounceAction.mPounceEnergy;

			// Ravage
			int targetNrRavageAttacks = td.mRavageAction.mNrRavageMiss + td.mRavageAction.mNrRavageDodge + td.mRavageAction.mNrRavageParry + td.mRavageAction.mNrRavageCrit + td.mRavageAction.mNrRavageHit;
			targetReport.setResult(CatSimStat.RAVAGES, targetNrRavageAttacks);
			targetReport.setResult(CatSimStat.RAVAGE_DPS, (double) td.mRavageAction.mRavageDamage / targetActiveTime);
			if (targetNrRavageAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_RAVAGE_MISS, (double) td.mRavageAction.mNrRavageMiss / targetNrRavageAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAVAGE_DODGE, (double) td.mRavageAction.mNrRavageDodge / targetNrRavageAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAVAGE_PARRY, (double) td.mRavageAction.mNrRavageParry / targetNrRavageAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAVAGE_CRIT, (double) td.mRavageAction.mNrRavageCrit / targetNrRavageAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAVAGE_HIT, (double) td.mRavageAction.mNrRavageHit / targetNrRavageAttacks * 100);
				targetReport.setResult(CatSimStat.RAVAGE_AVG_DMG, (double) td.mRavageAction.mRavageDamage / targetNrRavageAttacks);
				targetReport.setResult(CatSimStat.P_RAVAGE_TF, (double) td.mRavageAction.mNrRavageTF / targetNrRavageAttacks * 100);

				double pRavageFail = (double) (td.mRavageAction.mNrRavageMiss + td.mRavageAction.mNrRavageDodge + td.mRavageAction.mNrRavageParry) / targetNrRavageAttacks;
				effectiveEnergySpent += targetNrRavageAttacks * state.mModel.ravageCost * ((1 - pRavageFail) + 0.2 * pRavageFail);
				if (td.mRavageAction.mDidHardCastRavage == true) didHardCastRavage = true;
				if (td.mRavageAction.mDidStampedeRavage == true) didStampedeRavage = true;
			} else {
				targetReport.setResult(CatSimStat.P_RAVAGE_MISS, 0);
				targetReport.setResult(CatSimStat.P_RAVAGE_DODGE, 0);
				targetReport.setResult(CatSimStat.P_RAVAGE_PARRY, 0);
				targetReport.setResult(CatSimStat.P_RAVAGE_CRIT, 0);
				targetReport.setResult(CatSimStat.P_RAVAGE_HIT, 0);
				targetReport.setResult(CatSimStat.RAVAGE_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.P_RAVAGE_TF, 0);
			}
			ravageDamage += td.mRavageAction.mRavageDamage;
			nrRavageAttacks += targetNrRavageAttacks;
			nrRavageFail += td.mRavageAction.mNrRavageDodge + td.mRavageAction.mNrRavageMiss + td.mRavageAction.mNrRavageParry;
			nrRavageTF += td.mRavageAction.mNrRavageTF;
//			nrRavageOoC += td.mRavageAction.mNrRavageOoC;
			ravageEnergy += td.mRavageAction.mRavageEnergy;

			// Claw
			int targetNrClawAttacks = td.mClawAction.mNrClawMiss + td.mClawAction.mNrClawDodge + td.mClawAction.mNrClawParry + td.mClawAction.mNrClawCrit + td.mClawAction.mNrClawHit;
			targetReport.setResult(CatSimStat.CLAWS, targetNrClawAttacks);
			targetReport.setResult(CatSimStat.CLAW_DPS, (double) td.mClawAction.mClawDamage / targetActiveTime);
			if (targetNrClawAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_CLAW_MISS, (double) td.mClawAction.mNrClawMiss / targetNrClawAttacks * 100);
				targetReport.setResult(CatSimStat.P_CLAW_DODGE, (double) td.mClawAction.mNrClawDodge / targetNrClawAttacks * 100);
				targetReport.setResult(CatSimStat.P_CLAW_PARRY, (double) td.mClawAction.mNrClawParry / targetNrClawAttacks * 100);
				targetReport.setResult(CatSimStat.P_CLAW_CRIT, (double) td.mClawAction.mNrClawCrit / targetNrClawAttacks * 100);
				targetReport.setResult(CatSimStat.P_CLAW_HIT, (double) td.mClawAction.mNrClawHit / targetNrClawAttacks * 100);
				targetReport.setResult(CatSimStat.CLAW_AVG_DMG, (double) td.mClawAction.mClawDamage / targetNrClawAttacks);
				targetReport.setResult(CatSimStat.P_CLAW_TF, (double) td.mClawAction.mNrClawTF / targetNrClawAttacks * 100);

				double pClawFail = (double) (td.mClawAction.mNrClawMiss + td.mClawAction.mNrClawDodge + td.mClawAction.mNrClawParry) / targetNrClawAttacks;
				effectiveEnergySpent += (targetNrClawAttacks > 0) ? targetNrClawAttacks * state.mModel.clawCost * ((1 - pClawFail) + 0.2 * pClawFail) : 0;
			} else {
				targetReport.setResult(CatSimStat.P_CLAW_MISS, 0);
				targetReport.setResult(CatSimStat.P_CLAW_DODGE, 0);
				targetReport.setResult(CatSimStat.P_CLAW_PARRY, 0);
				targetReport.setResult(CatSimStat.P_CLAW_CRIT, 0);
				targetReport.setResult(CatSimStat.P_CLAW_HIT, 0);
				targetReport.setResult(CatSimStat.CLAW_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.P_CLAW_TF, 0);
			}
			clawDamage += td.mClawAction.mClawDamage;
			nrClawAttacks += targetNrClawAttacks;
			nrClawTF += td.mClawAction.mNrClawTF;
			nrClawFail += td.mClawAction.mNrClawMiss + td.mClawAction.mNrClawDodge + td.mClawAction.mNrClawParry;
			nrClawOoC += td.mClawAction.mNrClawOoC;
			clawEnergy += td.mClawAction.mClawEnergy;

			// Mangle
			int targetNrMangleAttacks = td.mMangleAction.mNrMangleMiss + td.mMangleAction.mNrMangleDodge + td.mMangleAction.mNrMangleParry + td.mMangleAction.mNrMangleCrit + td.mMangleAction.mNrMangleHit;
			targetReport.setResult(CatSimStat.MANGLES, targetNrMangleAttacks);
			targetReport.setResult(CatSimStat.MANGLE_DPS, (double) td.mMangleAction.mMangleDamage / targetActiveTime);
			targetReport.setResult(CatSimStat.MANGLE_UPTIME, td.mMangleAction.getUpTime() / targetActiveTime * 100);
			if (targetNrMangleAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_MANGLE_MISS, (double) td.mMangleAction.mNrMangleMiss / targetNrMangleAttacks * 100);
				targetReport.setResult(CatSimStat.P_MANGLE_DODGE, (double) td.mMangleAction.mNrMangleDodge / targetNrMangleAttacks * 100);
				targetReport.setResult(CatSimStat.P_MANGLE_PARRY, (double) td.mMangleAction.mNrMangleParry / targetNrMangleAttacks * 100);
				targetReport.setResult(CatSimStat.P_MANGLE_CRIT, (double) td.mMangleAction.mNrMangleCrit / targetNrMangleAttacks * 100);
				targetReport.setResult(CatSimStat.P_MANGLE_HIT, (double) td.mMangleAction.mNrMangleHit / targetNrMangleAttacks * 100);
				targetReport.setResult(CatSimStat.MANGLE_AVG_DMG, (double) td.mMangleAction.mMangleDamage / targetNrMangleAttacks);
				targetReport.setResult(CatSimStat.P_MANGLE_TF, (double) td.mMangleAction.mNrMangleTF / targetNrMangleAttacks * 100);

				double pMangleFail = (double) (td.mMangleAction.mNrMangleMiss + td.mMangleAction.mNrMangleDodge + td.mMangleAction.mNrMangleParry) / targetNrMangleAttacks;
				effectiveEnergySpent += (targetNrMangleAttacks > 0) ? targetNrMangleAttacks * state.mModel.mangleCost * ((1 - pMangleFail) + 0.2 * pMangleFail) : 0;
			} else {
				targetReport.setResult(CatSimStat.P_MANGLE_MISS, 0);
				targetReport.setResult(CatSimStat.P_MANGLE_DODGE, 0);
				targetReport.setResult(CatSimStat.P_MANGLE_PARRY, 0);
				targetReport.setResult(CatSimStat.P_MANGLE_CRIT, 0);
				targetReport.setResult(CatSimStat.P_MANGLE_HIT, 0);
				targetReport.setResult(CatSimStat.MANGLE_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.P_MANGLE_TF, 0);
			}
			mangleDamage += td.mMangleAction.mMangleDamage;
			nrMangleAttacks += targetNrMangleAttacks;
			nrMangleTF += td.mMangleAction.mNrMangleTF;
			nrMangleFail += td.mMangleAction.mNrMangleMiss + td.mMangleAction.mNrMangleDodge + td.mMangleAction.mNrMangleParry;
			nrMangleOoC += td.mMangleAction.mNrMangleOoC;
			mangleEnergy += td.mMangleAction.mMangleEnergy;

			// Shred
			int targetNrShredAttacks = td.mShredAction.mNrShredMiss + td.mShredAction.mNrShredDodge + td.mShredAction.mNrShredParry + td.mShredAction.mNrShredCrit + td.mShredAction.mNrShredHit;
			targetReport.setResult(CatSimStat.SHREDS, targetNrShredAttacks);
			targetReport.setResult(CatSimStat.SHRED_DPS, (double) td.mShredAction.mShredDamage / targetActiveTime);
			if (targetNrShredAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_SHRED_MISS, (double) td.mShredAction.mNrShredMiss / targetNrShredAttacks * 100);
				targetReport.setResult(CatSimStat.P_SHRED_DODGE, (double) td.mShredAction.mNrShredDodge / targetNrShredAttacks * 100);
				targetReport.setResult(CatSimStat.P_SHRED_PARRY, (double) td.mShredAction.mNrShredParry / targetNrShredAttacks * 100);
				targetReport.setResult(CatSimStat.P_SHRED_CRIT, (double) td.mShredAction.mNrShredCrit / targetNrShredAttacks * 100);
				targetReport.setResult(CatSimStat.P_SHRED_HIT, (double) td.mShredAction.mNrShredHit / targetNrShredAttacks * 100);
				targetReport.setResult(CatSimStat.SHRED_AVG_DMG, (double) td.mShredAction.mShredDamage / targetNrShredAttacks);
				targetReport.setResult(CatSimStat.P_SHRED_TF, (double) td.mShredAction.mNrShredTF / targetNrShredAttacks * 100);

				double pShredFail = (double) (td.mShredAction.mNrShredMiss + td.mShredAction.mNrShredDodge + td.mShredAction.mNrShredParry) / targetNrShredAttacks;
				effectiveEnergySpent += targetNrShredAttacks * state.mModel.mangleCost * ((1 - pShredFail) + 0.2 * pShredFail);
			} else {
				targetReport.setResult(CatSimStat.P_SHRED_MISS, 0);
				targetReport.setResult(CatSimStat.P_SHRED_DODGE, 0);
				targetReport.setResult(CatSimStat.P_SHRED_PARRY, 0);
				targetReport.setResult(CatSimStat.P_SHRED_CRIT, 0);
				targetReport.setResult(CatSimStat.P_SHRED_HIT, 0);
				targetReport.setResult(CatSimStat.SHRED_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.P_SHRED_TF, 0);
			}
			shredDamage += td.mShredAction.mShredDamage;
			nrShredAttacks += targetNrShredAttacks;
			nrShredTF += td.mShredAction.mNrShredTF;
			nrShredFail += td.mShredAction.mNrShredMiss + td.mShredAction.mNrShredDodge + td.mShredAction.mNrShredParry;
			nrShredOoC += td.mShredAction.mNrShredOoC;
			shredEnergy += td.mShredAction.mShredEnergy;

			// Rake
			int targetNrRakeAttacks = td.mRakeAction.mNrRakeMiss + td.mRakeAction.mNrRakeDodge + td.mRakeAction.mNrRakeParry + td.mRakeAction.mNrRakeCrit + td.mRakeAction.mNrRakeHit;
			int targetNrRakeTicks = td.mRakeAction.mNrRakeDotCrit + td.mRakeAction.mNrRakeDotHit;
			targetReport.setResult(CatSimStat.RAKES, targetNrRakeAttacks);
			targetReport.setResult(CatSimStat.RAKE_TICKS, targetNrRakeTicks);
			targetReport.setResult(CatSimStat.RAKE_DPS, (double) (td.mRakeAction.mRakeDamage + td.mRakeAction.mRakeDotDamage) / targetActiveTime);
			if (targetNrRakeAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.RAKE_DIRECT_DPS, (double) td.mRakeAction.mRakeDamage / encounterDuration);
				targetReport.setResult(CatSimStat.P_RAKE_MISS, (double) td.mRakeAction.mNrRakeMiss / targetNrRakeAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAKE_DODGE, (double) td.mRakeAction.mNrRakeDodge / targetNrRakeAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAKE_PARRY, (double) td.mRakeAction.mNrRakeParry / targetNrRakeAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAKE_CRIT, (double) td.mRakeAction.mNrRakeCrit / targetNrRakeAttacks * 100);
				targetReport.setResult(CatSimStat.P_RAKE_HIT, (double) td.mRakeAction.mNrRakeHit / targetNrRakeAttacks * 100);
				targetReport.setResult(CatSimStat.RAKE_AVG_DMG, (double) td.mRakeAction.mRakeDamage / targetNrRakeAttacks);

				targetReport.setResult(CatSimStat.RAKE_UPTIME, td.mRakeAction.getUpTime() / targetActiveTime * 100);
				targetReport.setResult(CatSimStat.RAKE_DOT_DPS, (double) td.mRakeAction.mRakeDotDamage / encounterDuration);
				if (td.mRakeAction.mRakeDotDamage > 0) {
					targetReport.setResult(CatSimStat.P_RAKE_DOT_CRIT, (double) td.mRakeAction.mNrRakeDotCrit / targetNrRakeTicks * 100);
					targetReport.setResult(CatSimStat.P_RAKE_DOT_HIT, (double) td.mRakeAction.mNrRakeDotHit / targetNrRakeTicks * 100);
					targetReport.setResult(CatSimStat.RAKE_DOT_AVG_DMG, (double) td.mRakeAction.mRakeDotDamage / targetNrRakeTicks);
				} else {
					targetReport.setResult(CatSimStat.P_RAKE_DOT_CRIT, 0);
					targetReport.setResult(CatSimStat.P_RAKE_DOT_HIT, 0);
					targetReport.setResult(CatSimStat.RAKE_DOT_AVG_DMG, 0);
				}

				double pRakeFail = (double) (td.mRakeAction.mNrRakeMiss + td.mRakeAction.mNrRakeDodge + td.mRakeAction.mNrRakeParry) / targetNrRakeAttacks;
				effectiveEnergySpent += targetNrRakeAttacks * state.mModel.rakeCost * ((1 - pRakeFail) + 0.2 * pRakeFail);
			} else {
				targetReport.setResult(CatSimStat.RAKE_DIRECT_DPS, 0);
				targetReport.setResult(CatSimStat.P_RAKE_MISS, 0);
				targetReport.setResult(CatSimStat.P_RAKE_DODGE, 0);
				targetReport.setResult(CatSimStat.P_RAKE_PARRY, 0);
				targetReport.setResult(CatSimStat.P_RAKE_CRIT, 0);
				targetReport.setResult(CatSimStat.P_RAKE_HIT, 0);
				targetReport.setResult(CatSimStat.RAKE_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.RAKE_UPTIME, 0);
				targetReport.setResult(CatSimStat.RAKE_DOT_DPS, 0);
				targetReport.setResult(CatSimStat.P_RAKE_DOT_CRIT, 0);
				targetReport.setResult(CatSimStat.P_RAKE_DOT_HIT, 0);
				targetReport.setResult(CatSimStat.RAKE_DOT_AVG_DMG, 0);
			}
			rakeDamage += td.mRakeAction.mRakeDamage + td.mRakeAction.mRakeDotDamage;
			nrRakeAttacks += targetNrRakeAttacks;
			nrRakeTF += td.mRakeAction.mNrRakeTF;
			nrRakeFail += td.mRakeAction.mNrRakeMiss + td.mRakeAction.mNrRakeDodge + td.mRakeAction.mNrRakeParry;
			nrRakeTicks += targetNrRakeTicks;
			nrRakeTicksTF += td.mRakeAction.mNrRakeDotTFTicks;
			nrRakeOoC += td.mRakeAction.mNrRakeOoC;
			rakeEnergy += td.mRakeAction.mRakeEnergy;

			// Rip
			int targetNrRipAttacks = td.mRipAction.mNrRipMiss + td.mRipAction.mNrRipDodge + td.mRipAction.mNrRipParry + td.mRipAction.mNrRipHit;
			int targetNrRipTicks = td.mRipAction.mNrRipDotCrit + td.mRipAction.mNrRipDotHit;
			targetReport.setResult(CatSimStat.RIPS, targetNrRipAttacks);
			targetReport.setResult(CatSimStat.RIP_TICKS, targetNrRipTicks);
			targetReport.setResult(CatSimStat.RIP_DPS, (double) td.mRipAction.mRipDamage / targetActiveTime);
			if (targetNrRipAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.RIPS_NORMAL, td.mRipAction.mNrRipBase +td.mRipAction.mNrRipFailBase);
				targetReport.setResult(CatSimStat.RIPS_BITW, targetNrRipAttacks - (td.mRipAction.mNrRipBase + td.mRipAction.mNrRipFailBase));

				targetReport.setResult(CatSimStat.P_RIP_MISS, (double) td.mRipAction.mNrRipMiss / targetNrRipAttacks * 100);
				targetReport.setResult(CatSimStat.P_RIP_DODGE, (double) td.mRipAction.mNrRipDodge / targetNrRipAttacks * 100);
				targetReport.setResult(CatSimStat.P_RIP_PARRY, (double) td.mRipAction.mNrRipParry / targetNrRipAttacks * 100);
				targetReport.setResult(CatSimStat.P_RIP_HIT, (double) td.mRipAction.mNrRipHit / targetNrRipAttacks * 100);

				targetReport.setResult(CatSimStat.RIP_UPTIME, td.mRipAction.getUpTime() / targetActiveTime * 100);
				if (td.mRipAction.mRipDamage > 0) {
					targetReport.setResult(CatSimStat.P_RIP_DOT_CRIT, (double) td.mRipAction.mNrRipDotCrit / targetNrRipTicks * 100);
					targetReport.setResult(CatSimStat.P_RIP_DOT_HIT, (double) td.mRipAction.mNrRipDotHit / targetNrRipTicks * 100);
					targetReport.setResult(CatSimStat.RIP_DOT_AVG_DMG, (double) td.mRipAction.mRipDamage / targetNrRipTicks);
				} else {
					targetReport.setResult(CatSimStat.P_RIP_DOT_CRIT, 0);
					targetReport.setResult(CatSimStat.P_RIP_DOT_HIT, 0);
					targetReport.setResult(CatSimStat.RIP_DOT_AVG_DMG, 0);
				}

				if (state.mModel.druid.getTalent(CatModel.BLOODINTHEWATER) > 0 && state.mModel.getBloodInTheWaterTime() > 0) {
					ripDamageDPE += td.mRipAction.mRipDamageBase;
					nrRipAttacksDPE += td.mRipAction.mNrRipBase + td.mRipAction.mNrRipFailBase;
					nrRipFailDPE += td.mRipAction.mNrRipFailBase;
				} else {
					ripDamageDPE += td.mRipAction.mRipDamage;
					nrRipAttacksDPE += targetNrRipAttacks;
					nrRipFailDPE += td.mRipAction.mNrRipMiss + td.mRipAction.mNrRipDodge + td.mRipAction.mNrRipParry;
				}

				double pRipFail = (double) (td.mRipAction.mNrRipMiss + td.mRipAction.mNrRipDodge + td.mRipAction.mNrRipParry) / targetNrRipAttacks;
				effectiveEnergySpent += targetNrRipAttacks * state.mModel.ripCost * ((1 - pRipFail) + 0.2 * pRipFail);
			} else {
				targetReport.setResult(CatSimStat.RIPS_NORMAL, 0);
				targetReport.setResult(CatSimStat.RIPS_BITW, 0);
				targetReport.setResult(CatSimStat.P_RIP_MISS, 0);
				targetReport.setResult(CatSimStat.P_RIP_DODGE, 0);
				targetReport.setResult(CatSimStat.P_RIP_PARRY, 0);
				targetReport.setResult(CatSimStat.P_RIP_HIT, 0);
				targetReport.setResult(CatSimStat.RIP_UPTIME, 0);
				targetReport.setResult(CatSimStat.P_RIP_DOT_CRIT, 0);
				targetReport.setResult(CatSimStat.P_RIP_DOT_HIT, 0);
				targetReport.setResult(CatSimStat.RIP_DOT_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.RIP_DPE, 0);
			}
			ripDamage += td.mRipAction.mRipDamage;
			nrRipTicks += targetNrRipTicks;
			nrRipTicksTF += td.mRipAction.mNrRipDotTFTicks;
			nrRipCPs += td.mRipAction.mNrRipCPs;
			nrRipOoC += td.mRipAction.mNrRipOoC;
			ripEnergy += td.mRipAction.mRipEnergy;

			// Ferocious Bite
			int targetNrBiteAttacks = td.mFerociousBiteAction.mNrBiteMiss + td.mFerociousBiteAction.mNrBiteDodge + td.mFerociousBiteAction.mNrBiteParry + td.mFerociousBiteAction.mNrBiteCrit + td.mFerociousBiteAction.mNrBiteHit;
			targetReport.setResult(CatSimStat.FEROCIOUS_BITES, targetNrBiteAttacks);
			targetReport.setResult(CatSimStat.FEROCIOUS_BITE_DPS, (double) td.mFerociousBiteAction.mBiteDamage / targetActiveTime);
			if (targetNrBiteAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.FEROCIOUS_BITES_NORMAL, td.mFerociousBiteAction.mNrBiteBase);
				targetReport.setResult(CatSimStat.FEROCIOUS_BITES_BITW, targetNrBiteAttacks - td.mFerociousBiteAction.mNrBiteBase); // Unlike Rip this includes all attacks.  Yay, inconsistency.
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_MISS, (double) td.mFerociousBiteAction.mNrBiteMiss / targetNrBiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_DODGE, (double) td.mFerociousBiteAction.mNrBiteDodge / targetNrBiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_PARRY, (double) td.mFerociousBiteAction.mNrBiteParry / targetNrBiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_CRIT, (double) td.mFerociousBiteAction.mNrBiteCrit / targetNrBiteAttacks * 100);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_HIT, (double) td.mFerociousBiteAction.mNrBiteHit / targetNrBiteAttacks * 100);
				targetReport.setResult(CatSimStat.FEROCIOUS_BITE_AVG_EXTRA_ENERGY, (double) td.mFerociousBiteAction.mBiteExtraEnergy / targetNrBiteAttacks);
				targetReport.setResult(CatSimStat.FEROCIOUS_BITE_AVG_DMG, (double) td.mFerociousBiteAction.mBiteDamage / targetNrBiteAttacks);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_TF, (double) td.mFerociousBiteAction.mNrBiteTF / targetNrBiteAttacks * 100);

				double pBiteFail = (double) (td.mFerociousBiteAction.mNrBiteMiss + td.mFerociousBiteAction.mNrBiteDodge + td.mFerociousBiteAction.mNrBiteParry) / targetNrBiteAttacks;
				effectiveEnergySpent += targetNrBiteAttacks * (state.mModel.biteCost + td.mFerociousBiteAction.mBiteExtraEnergy / targetNrBiteAttacks) * ((1 - pBiteFail) + 0.2 * pBiteFail);
			} else {
				targetReport.setResult(CatSimStat.FEROCIOUS_BITES_NORMAL, 0);
				targetReport.setResult(CatSimStat.FEROCIOUS_BITES_BITW, 0);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_MISS, 0);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_DODGE, 0);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_PARRY, 0);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_CRIT, 0);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_HIT, 0);
				targetReport.setResult(CatSimStat.FEROCIOUS_BITE_AVG_EXTRA_ENERGY, 0);
				targetReport.setResult(CatSimStat.FEROCIOUS_BITE_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.P_CP_FEROCIOUS_BITE, 0);
				targetReport.setResult(CatSimStat.P_FEROCIOUS_BITE_TF, 0);
			}
			biteDamage += td.mFerociousBiteAction.mBiteDamage;
			nrBiteAttacks += targetNrBiteAttacks;
			nrBiteFail += td.mFerociousBiteAction.mNrBiteMiss + td.mFerociousBiteAction.mNrBiteDodge + td.mFerociousBiteAction.mNrBiteParry;
			nrBiteTF += td.mFerociousBiteAction.mNrBiteTF;
			nrBiteCPs += td.mFerociousBiteAction.mNrBiteCPs;
			nrBiteExtraEnergy += td.mFerociousBiteAction.mBiteExtraEnergy;
			nrBiteOoC += td.mFerociousBiteAction.mNrBiteOoC;
			biteEnergy += td.mFerociousBiteAction.mBiteEnergy;

			// Maim
			int targetNrMaimAttacks = td.mMaimAction.mNrMaimMiss + td.mMaimAction.mNrMaimDodge + td.mMaimAction.mNrMaimParry + td.mMaimAction.mNrMaimCrit + td.mMaimAction.mNrMaimHit;
			targetReport.setResult(CatSimStat.MAIMS, targetNrMaimAttacks);
			targetReport.setResult(CatSimStat.MAIM_DPS, (double) td.mMaimAction.mMaimDamage / targetActiveTime);
			if (targetNrMaimAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_MAIM_MISS, (double) td.mMaimAction.mNrMaimMiss / targetNrMaimAttacks * 100);
				targetReport.setResult(CatSimStat.P_MAIM_DODGE, (double) td.mMaimAction.mNrMaimDodge / targetNrMaimAttacks * 100);
				targetReport.setResult(CatSimStat.P_MAIM_PARRY, (double) td.mMaimAction.mNrMaimParry / targetNrMaimAttacks * 100);
				targetReport.setResult(CatSimStat.P_MAIM_CRIT, (double) td.mMaimAction.mNrMaimCrit / targetNrMaimAttacks * 100);
				targetReport.setResult(CatSimStat.P_MAIM_HIT, (double) td.mMaimAction.mNrMaimHit / targetNrMaimAttacks * 100);
				targetReport.setResult(CatSimStat.MAIM_AVG_DMG, (double) td.mMaimAction.mMaimDamage / targetNrMaimAttacks);
				targetReport.setResult(CatSimStat.P_MAIM_TF, (double) td.mMaimAction.mNrMaimTF / targetNrMaimAttacks * 100);

				double pMaimFail = (double) (td.mMaimAction.mNrMaimMiss + td.mMaimAction.mNrMaimDodge + td.mMaimAction.mNrMaimParry) / targetNrMaimAttacks;
				effectiveEnergySpent += targetNrMaimAttacks * state.mModel.maimCost * ((1 - pMaimFail) + 0.2 * pMaimFail);
			} else {
				targetReport.setResult(CatSimStat.P_MAIM_MISS, 0);
				targetReport.setResult(CatSimStat.P_MAIM_DODGE, 0);
				targetReport.setResult(CatSimStat.P_MAIM_PARRY, 0);
				targetReport.setResult(CatSimStat.P_MAIM_CRIT, 0);
				targetReport.setResult(CatSimStat.P_MAIM_HIT, 0);
				targetReport.setResult(CatSimStat.MAIM_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.P_CP_MAIM, 0);
				targetReport.setResult(CatSimStat.P_MAIM_TF, 0);
			}
			maimDamage += td.mMaimAction.mMaimDamage;
			nrMaimAttacks += targetNrMaimAttacks;
			nrMaimFail += td.mMaimAction.mNrMaimMiss + td.mMaimAction.mNrMaimDodge + td.mMaimAction.mNrMaimParry;
			nrMaimTF += td.mMaimAction.mNrMaimTF;
			nrMaimCPs += td.mMaimAction.mNrMaimCPs;
			nrMaimOoC += td.mMaimAction.mNrMaimOoC;
			maimEnergy += td.mMaimAction.mMaimEnergy;

			// Swipe
			int targetNrSwipeAttacks = swipeStats.mNrSwipeMiss + swipeStats.mNrSwipeDodge + swipeStats.mNrSwipeParry + swipeStats.mNrSwipeCrit + swipeStats.mNrSwipeHit;
			targetReport.setResult(CatSimStat.SWIPES, targetNrSwipeAttacks);
			targetReport.setResult(CatSimStat.SWIPE_DPS, (double) swipeStats.mSwipeDamage / targetActiveTime);
			if (targetNrSwipeAttacks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.P_SWIPE_MISS, (double) swipeStats.mNrSwipeMiss / targetNrSwipeAttacks * 100);
				targetReport.setResult(CatSimStat.P_SWIPE_DODGE, (double) swipeStats.mNrSwipeDodge / targetNrSwipeAttacks * 100);
				targetReport.setResult(CatSimStat.P_SWIPE_PARRY, (double) swipeStats.mNrSwipeParry / targetNrSwipeAttacks * 100);
				targetReport.setResult(CatSimStat.P_SWIPE_CRIT, (double) swipeStats.mNrSwipeCrit / targetNrSwipeAttacks * 100);
				targetReport.setResult(CatSimStat.P_SWIPE_HIT, (double) swipeStats.mNrSwipeHit / targetNrSwipeAttacks * 100);
				targetReport.setResult(CatSimStat.SWIPE_AVG_DMG, (double) swipeStats.mSwipeDamage / targetNrSwipeAttacks);
			} else {
				targetReport.setResult(CatSimStat.P_SWIPE_MISS, 0);
				targetReport.setResult(CatSimStat.P_SWIPE_DODGE, 0);
				targetReport.setResult(CatSimStat.P_SWIPE_PARRY, 0);
				targetReport.setResult(CatSimStat.P_SWIPE_CRIT, 0);
				targetReport.setResult(CatSimStat.P_SWIPE_HIT, 0);
				targetReport.setResult(CatSimStat.SWIPE_AVG_DMG, 0);
			}
			swipeDamage += swipeStats.mSwipeDamage;

			// Fiery Claws (Tier 12 2pc)
			int nrFieryClawsTicks = td.mTier_12_2pcEv.mNrFieryClawsTicks;
			targetReport.setResult(CatSimStat.FIERY_CLAWS_TICKS, nrFieryClawsTicks);
			targetReport.setResult(CatSimStat.FIERY_CLAWS_DPS, (double) td.mTier_12_2pcEv.mFieryClawsDmg / encounterDuration);
			if (nrFieryClawsTicks > 0 && didDamage) {
				targetReport.setResult(CatSimStat.FIERY_CLAWS_AVG_DMG, (double) td.mTier_12_2pcEv.mFieryClawsDmg / nrFieryClawsTicks);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_ROLLED, td.mTier_12_2pcEv.mNrFieryClawsRolled);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_ROLLED_DMG, td.mTier_12_2pcEv.mFieryClawsDmgRolled);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_MUNCHED, td.mTier_12_2pcEv.mNrFieryClawsMunch);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_MUNCHED_DMG, td.mTier_12_2pcEv.mFieryClawsDmgLostMunch);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_REFRESH_LOST, td.mTier_12_2pcEv.mNrFieryClawsRefreshLost);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_REFRESH_LOST_DMG, td.mTier_12_2pcEv.mFieryClawsDmgLostRefresh);
			} else {
				targetReport.setResult(CatSimStat.FIERY_CLAWS_AVG_DMG, 0);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_ROLLED, 0);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_ROLLED_DMG, 0);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_MUNCHED, 0);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_MUNCHED_DMG, 0);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_REFRESH_LOST, 0);
				targetReport.setResult(CatSimStat.FIERY_CLAWS_REFRESH_LOST_DMG, 0);
			}
			fieryClawsDamage += td.mTier_12_2pcEv.mFieryClawsDmg;

			// TODO: Damage breakdown by ability here

			// Damage Procs
			targetReport.setResult(CatSimStat.OTHER_DPS, (double) (td.mProcPhysDamage + td.mProcSpellDamage) / targetActiveTime);
			otherDamage += td.mProcPhysDamage + td.mProcSpellDamage;

			targetReport.mShouldReportParry = td.mShouldReportParry;

			perTargetReports.put(targetName, targetReport);

			totalDamage += targetTotalDamage;
		}

		//
		// Put the rest of the report together
		//

		boolean didDamage = totalDamage > 0;

		report.setResult(Stat.DAMAGE_DEALT_PER_SECOND, totalDamage / encounterDuration);
		report.setResult(CatSimStat.TOTAL_DAMAGE, totalDamage);

		// Player buffs
		report.setResult(CatSimStat.HEROISM_UPTIME, state.mHeroismEv.getUpTime() / encounterDuration * 100);
		report.setResult(CatSimStat.UNHOLY_FRENZY_UPTIME, state.mUnholyFrenzyEv.getUpTime() / encounterDuration * 100);
		if (state.mModel.buffsDebuffs.isBuff(Buff.TRICKSOFTHETRADE)) {
			// Sigh.  The TOTT event returns 100% for getUpTime, so fake this.  It's correct anyway.
			report.setResult(CatSimStat.TOTT_UPTIME, state.mModel.GetCooldownUptime(state.mModel.tottBuffDuration, state.mModel.tottCDDuration, (int) encounterDuration) / encounterDuration * 100);
		} else report.setResult(CatSimStat.TOTT_UPTIME, 0);

		// Energy Regen
		// Notes:
		//  * Yes, spent will be < 100 higher than gained.  Don't care if people are too dumb to figure out why.
		//  * ENERGY_EFFECTIVE_REGEN_RATE is calculated later.
		report.setResult(CatSimStat.ENERGY_GAINED, state.mEnergyTickEv.mEnergyGained + state.mTigersFuryEv.mKotJEnergyGained + (state.mPrimalMadnessEnergyGained - state.mPrimalMadnessEnergyLost));
		report.setResult(CatSimStat.ENERGY_GAINED_PASSIVE, state.mEnergyTickEv.mEnergyGained);
		report.setResult(CatSimStat.ENERGY_GAINED_KOTJ, state.mTigersFuryEv.mKotJEnergyGained);
		report.setResult(CatSimStat.ENERGY_GAINED_PRIMAL_MADNESS, state.mPrimalMadnessEnergyGained - state.mPrimalMadnessEnergyLost);
		if (state.mPrimalMadnessTriggers > 0) {
			report.setResult(CatSimStat.PRIMAL_MADNESS_AVG_ENERGY_GAINED, (double) (state.mPrimalMadnessEnergyGained - state.mPrimalMadnessEnergyLost) / state.mPrimalMadnessTriggers);
		} else {
			report.setResult(CatSimStat.PRIMAL_MADNESS_AVG_ENERGY_GAINED, 0);
		}
		report.setResult(CatSimStat.ENERGY_LOST_SHIFTING, state.mEnergyTickEv.mEnergyLostShifting);
		report.setResult(CatSimStat.ENERGY_SPENT, state.mEnergySpent);
		report.setResult(CatSimStat.ENERGY_OVERFLOW, state.mEnergyTickEv.mEnergyOverflow);
		report.setResult(CatSimStat.OOC_PROCS, state.mOocEv.mNrOoCProcs);
		if (state.mOocEv.mNrOoCProcs > 0) {
			report.setResult(CatSimStat.OOC_PROCS_CONSUMED, state.mOocEv.mNrOoCProcsConsumed);
			report.setResult(CatSimStat.OOC_PROCS_WASTED, state.mOocEv.mNrOoCProcsWasted);
			report.setResult(CatSimStat.OOC_EFFECTIVE_PPM, (double) state.mOocEv.mNrOoCProcs / (encounterDuration / 60.0));
			report.setResult(CatSimStat.OOC_AVG_ENERGY_RETURN, (double) state.mOocEv.mOocEnergyReturn / state.mOocEv.mNrOoCProcs);
		} else {
			report.setResult(CatSimStat.OOC_PROCS_CONSUMED, 0);
			report.setResult(CatSimStat.OOC_PROCS_WASTED, 0);
			report.setResult(CatSimStat.OOC_EFFECTIVE_PPM, 0);
			report.setResult(CatSimStat.OOC_AVG_ENERGY_RETURN, 0);
		}
		report.setResult(CatSimStat.BERSERKS, state.mBerserkEv.mNrBerserk);
		report.setResult(CatSimStat.BERSERK_UPTIME, state.mBerserkEv.getUpTime() / encounterDuration * 100);
		if (state.mBerserkEv.mNrBerserk > 0) {
			report.setResult(CatSimStat.BERSERK_AVG_DURATION, state.mBerserkEv.getUpTime() / state.mBerserkEv.mNrBerserk);
		} else {
			report.setResult(CatSimStat.BERSERK_AVG_DURATION, 0);
		}

		// CP Generation
		report.setResult(CatSimStat.CP_GAINED, state.mComboPointsGained);
		report.setResult(CatSimStat.CP_GAINED_PRIMAL_FURY, state.mComboPointsGainedPrimalFury);
		report.setResult(CatSimStat.CP_SPENT, state.mComboPointsSpent);
		report.setResult(CatSimStat.CP_OVERFLOW, state.mComboPointsOverflow);
		report.setResult(CatSimStat.CP_GEN_RATE, (double) state.mComboPointsGained / encounterDuration);

		// CP Sinks
		if (state.mComboPointsSpent > 0) {
			report.setResult(CatSimStat.P_CP_RIP, (double) nrRipCPs / state.mComboPointsSpent * 100);
			report.setResult(CatSimStat.P_CP_FEROCIOUS_BITE, (double) nrBiteCPs / state.mComboPointsSpent * 100);
			report.setResult(CatSimStat.P_CP_MAIM, (double) nrMaimCPs / state.mComboPointsSpent * 100);
			report.setResult(CatSimStat.P_CP_SAVAGE_ROAR, (double) state.mSavageRoarEv.mNrSrCPs / state.mComboPointsSpent * 100);
		} else {
			report.setResult(CatSimStat.P_CP_RIP, 0);
			report.setResult(CatSimStat.P_CP_FEROCIOUS_BITE, 0);
			report.setResult(CatSimStat.P_CP_MAIM, 0);
			report.setResult(CatSimStat.P_CP_SAVAGE_ROAR, 0);
		}

		// Tiger's Fury
		long nrTF = state.mTigersFuryEv.mNrTF;
		report.setResult(CatSimStat.TIGERS_FURYS, state.mTigersFuryEv.mNrTF);
		report.setResult(CatSimStat.TIGERS_FURY_UPTIME, state.mTigersFuryEv.getUpTime() / encounterDuration * 100);
		if (nrTF > 0) {
			report.setResult(CatSimStat.TIGERS_FURY_INTERVAL, encounterDuration / nrTF);
		} else {
			report.setResult(CatSimStat.TIGERS_FURY_INTERVAL, 0);
		}
		report.setResult(CatSimStat.P_POUNCE_TF, (nrPounceTicks > 0) ? (double) nrPounceTicksTF / nrPounceTicks * 100 : 0);
		report.setResult(CatSimStat.P_RAVAGE_TF, (nrRavageAttacks > 0) ? (double) nrRavageTF / nrRavageAttacks * 100 : 0);
		report.setResult(CatSimStat.P_CLAW_TF, (nrClawAttacks > 0) ? (double) nrClawTF / nrClawAttacks * 100 : 0);
		report.setResult(CatSimStat.P_MANGLE_TF, (nrMangleAttacks > 0) ? (double) nrMangleTF / nrMangleAttacks * 100 : 0);
		report.setResult(CatSimStat.P_SHRED_TF, (nrShredAttacks > 0) ? (double) nrShredTF / nrShredAttacks * 100 : 0);
		report.setResult(CatSimStat.P_RAKE_TF, (nrRakeAttacks > 0) ? (double) (nrRakeTF + nrRakeTicksTF) / (nrRakeAttacks + nrRakeTicks) * 100 : 0);
		report.setResult(CatSimStat.P_RIP_TF, (nrRipTicks) > 0 ? (double) nrRipTicksTF / nrRipTicks * 100 : 0);
		report.setResult(CatSimStat.P_FEROCIOUS_BITE_TF, (nrBiteAttacks > 0) ? (double) nrBiteTF / nrBiteAttacks * 100 : 0);
		report.setResult(CatSimStat.P_MAIM_TF, (nrMaimAttacks > 0) ? (double) nrMaimTF / nrMaimAttacks * 100 : 0);
		report.setResult(CatSimStat.P_SWIPE_TF, (state.mSwipeAction.mNrSwipes > 0) ? (double) state.mSwipeAction.mNrSwipeTF / state.mSwipeAction.mNrSwipes * 100 : 0);

		// Feral Charge
		report.setResult(CatSimStat.FERAL_CHARGES, state.mFeralChargeAction.mNrFeralCharge);
		effectiveEnergySpent += state.mFeralChargeAction.mNrFeralCharge * state.mModel.feralChargeCost;

		// Swipe
		effectiveEnergySpent += state.mSwipeAction.mNrSwipes * state.mModel.swipeCost;

		// Savage Roar
		int nrSavageRoars = state.mSavageRoarEv.mNrSR;
		report.setResult(CatSimStat.SAVAGE_ROARS, nrSavageRoars);
		if (nrSavageRoars > 0 && didDamage) {
			report.setResult(CatSimStat.SAVAGE_ROAR_AVG_CPS, state.mSavageRoarEv.mNrSrCPs / nrSavageRoars);
			report.setResult(CatSimStat.SAVAGE_ROAR_UPTIME, state.mSavageRoarEv.getUpTime() / encounterDuration * 100);

			effectiveEnergySpent += state.mSavageRoarEv.mNrSR * state.mModel.srCost;
		} else {
			report.setResult(CatSimStat.SAVAGE_ROAR_AVG_CPS, 0);
			report.setResult(CatSimStat.SAVAGE_ROAR_UPTIME, 0);
		}

		// White attacks and Fury Swipe
		report.setResult(CatSimStat.WHITE_ATTACK_UPTIME, state.mMeleeSwingEv.getUpTime() / encounterDuration * 100);
		report.setResult(CatSimStat.WHITE_ATTACK_SWING_TIMER, state.mMeleeSwingEv.getUpTime() / nrWhiteAttacks);
		report.setResult(CatSimStat.FURY_SWIPES_INTERVAL, state.mMeleeSwingEv.getUpTime() / nrFurySwipes);

		report.mPerTargetReports = perTargetReports;
		report.mSortedTargetList = state.mTargetSpawnOrderList;

		// DPE
		if (nrRavageAttacks > 0) {
			int cost = (didStampedeRavage) ? state.mModel.ravageCostStampede : state.mModel.ravageCost;
			if (cost == 0 || (didStampedeRavage && didHardCastRavage)) {
				report.setResult(CatSimStat.RAVAGE_DPE, 0);
				report.mHaveValidRavageDPE = false;
			} else
				report.setResult(CatSimStat.RAVAGE_DPE, (nrRavageAttacks > 0) ? (double) ravageDamage / (nrRavageAttacks * cost * ((1 - nrRavageFail / nrRavageAttacks) + 0.2 * nrRavageFail / nrRavageAttacks)) : 0);
		} else report.setResult(CatSimStat.RAVAGE_DPE, 0);
		report.setResult(CatSimStat.POUNCE_DPE, (nrPounceAttacks > 0) ? (double) pounceDamage / (nrPounceAttacks * state.mModel.pounceCost * ((1 - nrPounceFail / nrPounceAttacks) + 0.2 * nrPounceFail / nrPounceAttacks)) : 0);
		report.setResult(CatSimStat.CLAW_DPE, (nrClawAttacks > 0) ? (double) clawDamage / (nrClawAttacks * state.mModel.clawCost * ((1 - nrClawFail / nrClawAttacks) + 0.2 * nrClawFail / nrClawAttacks)) : 0);
		report.setResult(CatSimStat.MANGLE_DPE, (nrMangleAttacks > 0) ? (double) mangleDamage / (nrMangleAttacks * state.mModel.mangleCost * ((1 - nrMangleFail / nrMangleAttacks) + 0.2 * nrMangleFail / nrMangleAttacks)) : 0);
		report.setResult(CatSimStat.SHRED_DPE, (nrShredAttacks > 0) ? (double) shredDamage / (nrShredAttacks * state.mModel.shredCost * ((1 - nrShredFail / nrShredAttacks) + 0.2 * nrShredFail / nrShredAttacks)) : 0);
		report.setResult(CatSimStat.RAKE_DPE, (nrRakeAttacks > 0) ? (double) rakeDamage / (nrRakeAttacks * state.mModel.rakeCost * ((1 - nrRakeFail / nrRakeAttacks) + 0.2 * nrRakeFail / nrRakeAttacks)) : 0);
		report.setResult(CatSimStat.RIP_DPE, (nrRipAttacksDPE > 0) ? (double) ripDamageDPE / (nrRipAttacksDPE * state.mModel.ripCost * ((1 - nrRipFailDPE / nrRipAttacksDPE) + 0.2 * nrRipFailDPE / nrRipAttacksDPE)) : 0);
		report.setResult(CatSimStat.FEROCIOUS_BITE_DPE, (nrBiteAttacks > 0) ? (double) biteDamage / (nrBiteAttacks * (state.mModel.biteCost + ((double) nrBiteExtraEnergy / nrBiteAttacks)) * ((1 - nrBiteFail / nrBiteAttacks) + 0.2 * nrBiteFail / nrBiteAttacks)) : 0);
		report.setResult(CatSimStat.MAIM_DPE, (nrMaimAttacks > 0) ? (double) maimDamage / (nrMaimAttacks * state.mModel.maimCost * (( 1 - nrMaimFail / nrMaimAttacks) + 0.2 * nrMaimFail / nrMaimAttacks)) : 0);
		report.setResult(CatSimStat.SAVAGE_ROAR_DPE, (nrSavageRoars > 0) ? (double) state.mMeleeSwingEv.mSRWhiteDamage / nrSavageRoars / state.mModel.srCost : 0);
		report.setResult(CatSimStat.SWIPE_DPE, (state.mSwipeAction.mNrSwipes > 0) ? (double) swipeDamage / state.mSwipeAction.mNrSwipes / state.mModel.swipeCost : 0);

		// Effective Energy Regeneration Rate
		double effectiveEnergyRegenRate = (effectiveEnergySpent - (state.mModel.baseEnergyCap - state.mEnergyTickEv.mEnergy)) / encounterDuration;
		report.setResult(CatSimStat.ENERGY_EFFECTIVE_REGEN_RATE, effectiveEnergyRegenRate);

		// Damage breakdown
		if (didDamage) {
			report.setResult(CatSimStat.P_WHITE_ATTACK_DMG, (double) whiteDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_FURY_SWIPES_DMG, (double) furySwipesDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_POUNCE_DMG, (double) pounceDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_RAVAGE_DMG, (double) ravageDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_CLAW_DMG, (double) clawDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_MANGLE_DMG, (double) mangleDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_SHRED_DMG, (double) shredDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_RAKE_DMG, (double) rakeDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_RIP_DMG, (double) ripDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_FEROCIOUS_BITE_DMG, (double) biteDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_MAIM_DMG, (double) maimDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_SWIPE_DMG, (double) swipeDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_FIERY_CLAWS_DMG, (double) fieryClawsDamage / totalDamage * 100);
			report.setResult(CatSimStat.P_OTHER_DMG, (double) otherDamage / totalDamage * 100);

			report.setResult(CatSimStat.P_ENERGY_POUNCE, (double) pounceEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_RAVAGE, (double) ravageEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_CLAW, (double) clawEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_MANGLE, (double) mangleEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_SHRED, (double) shredEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_RAKE, (double) rakeEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_RIP, (double) ripEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_FEROCIOUS_BITE, (double) biteEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_MAIM, (double) maimEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_SAVAGE_ROAR, (double) state.mSavageRoarEv.mSrEnergy / state.mEnergySpent * 100);
			report.setResult(CatSimStat.P_ENERGY_SWIPE, (double) state.mSwipeAction.mSwipeEnergy / state.mEnergySpent * 100);
		} else {
			report.setResult(CatSimStat.P_WHITE_ATTACK_DMG, 0);
			report.setResult(CatSimStat.P_FURY_SWIPES_DMG, 0);
			report.setResult(CatSimStat.P_POUNCE_DMG, 0);
			report.setResult(CatSimStat.P_RAVAGE_DMG, 0);
			report.setResult(CatSimStat.P_CLAW_DMG, 0);
			report.setResult(CatSimStat.P_MANGLE_DMG, 0);
			report.setResult(CatSimStat.P_SHRED_DMG, 0);
			report.setResult(CatSimStat.P_RAKE_DMG, 0);
			report.setResult(CatSimStat.P_RIP_DMG, 0);
			report.setResult(CatSimStat.P_FEROCIOUS_BITE_DMG, 0);
			report.setResult(CatSimStat.P_SWIPE_DMG, 0);
			report.setResult(CatSimStat.P_FIERY_CLAWS_DMG, 0);
			report.setResult(CatSimStat.P_OTHER_DMG, 0);

			report.setResult(CatSimStat.P_ENERGY_POUNCE, 0);
			report.setResult(CatSimStat.P_ENERGY_RAVAGE, 0);
			report.setResult(CatSimStat.P_ENERGY_MANGLE, 0);
			report.setResult(CatSimStat.P_ENERGY_SHRED, 0);
			report.setResult(CatSimStat.P_ENERGY_RAKE, 0);
			report.setResult(CatSimStat.P_ENERGY_RIP, 0);
			report.setResult(CatSimStat.P_ENERGY_FEROCIOUS_BITE, 0);
			report.setResult(CatSimStat.P_ENERGY_MAIM, 0);
			report.setResult(CatSimStat.P_ENERGY_SAVAGE_ROAR, 0);
			report.setResult(CatSimStat.P_ENERGY_SWIPE, 0);
		}
		if (state.mOocEv.mNrOoCProcsConsumed > 0) {
//			report.setResult(CatSimStat.P_OOC_RAVAGE, (double) nrRavageOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_CLAW, (double) nrClawOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_MANGLE, (double) nrMangleOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_SHRED, (double) nrShredOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_RAKE, (double) nrRakeOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_RIP, (double) nrRipOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_FEROCIOUS_BITE, (double) nrBiteOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_MAIM, (double) nrMaimOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
			report.setResult(CatSimStat.P_OOC_SWIPE, (double) state.mSwipeAction.mNrSwipeOoC / state.mOocEv.mNrOoCProcsConsumed * 100);
		} else {
//			report.setResult(CatSimStat.P_OOC_RAVAGE, 0);
			report.setResult(CatSimStat.P_OOC_CLAW, 0);
			report.setResult(CatSimStat.P_OOC_MANGLE, 0);
			report.setResult(CatSimStat.P_OOC_SHRED, 0);
			report.setResult(CatSimStat.P_OOC_RAKE, 0);
			report.setResult(CatSimStat.P_OOC_RIP, 0);
			report.setResult(CatSimStat.P_OOC_FEROCIOUS_BITE, 0);
			report.setResult(CatSimStat.P_OOC_MAIM, 0);
			report.setResult(CatSimStat.P_OOC_SWIPE, 0);
		}

		report.setResult(CatSimStat.POUNCES, nrPounceAttacks);
		report.setResult(CatSimStat.RAVAGES, nrRavageAttacks);
		report.setResult(CatSimStat.CLAWS, nrClawAttacks);
		report.setResult(CatSimStat.MANGLES, nrMangleAttacks);
		report.setResult(CatSimStat.SHREDS, nrShredAttacks);
		report.setResult(CatSimStat.FEROCIOUS_BITES, nrBiteAttacks);
		report.setResult(CatSimStat.MAIMS, nrMaimAttacks);
		report.setResult(CatSimStat.SWIPES, state.mSwipeAction.mNrSwipes);
		report.setResult(CatSimStat.RAKES, nrRakeAttacks);
		report.setResult(CatSimStat.RIP_TICKS, nrRipTicks);

		// Proc effects
		report.mProcUptimes = new HashMap<ProcEffect, Double>();
		for (SimulationProc proc : state.mSimEngine.getProcs()) {
			ProcEffect procEffect = proc.getProcEffect();
			if (procEffect.buffDuration == 0)
				report.mProcUptimes.put(proc.getProcEffect(), (double) proc.mNrProcs);
			else
				report.mProcUptimes.put(proc.getProcEffect(), proc.getUpTime() / encounterDuration);
		}

		// Damage Log
		if (state.mLogDamageEv != null) report.mDamageLog = state.mLogDamageEv.mDamageDone;

		return report;
	}

	@Override
	public boolean canPerformAction(IStatus status, IAction a) {
		Action action = (Action) a;
		CatModelSimState state = (CatModelSimState) status;

		// Always allow external buffs and potion use, it's up to the script to not be a retard.
		if (action == Action.POTION || action == Action.SHATTERINGTHROW ||
				action == Action.HEROISM || action == Action.UNHOLYFRENZY) return true;

		// Don't allow anything that requires player action when Rebirth or Tranquility is casting.
		if (state.mRebirthEv.isCasting() || state.mTranquilityEv.isCasting()) return false;

		if (!state.isInMeleeRange()) {
			// Disallow actions that require melee range if the druid is away from target.
			// Primarily to allow for Stampede + Ravage.
			switch (action) {
			case FERALCHARGE:
				// Enforce time away from target here (FERALCHARGE is the only gap closer).
				if (state.mRunAwayEv.isUp()) return false;
				break;
			case AUTOATTACK_START:
			case POUNCE:
			case RAVAGE:
			case CLAW:
			case MANGLE:
			case RAKE:
			case SHRED:
			case RIP:
			case BITE:
			case MAIM:
			case ROAR:
			case SWIPE:
			case RUNAWAY:	// You are already out of melee range.
			case SKULLBASH:	// Technically this is a gap closer, but for now make it require point blank range.
				return false;
			}
		}

		// XXX: HACK.  Hold the GCD for the Tranquility or Rebirth here.
		if (state.nextUtilityCastIn() < 1.0) {
			if (action != Action.SKULLBASH && action != Action.FERALCHARGE &&
					action != Action.BERSERK && action != Action.TIGERSFURY && action != Action.RUNAWAY) return false;
		}

		return true;
	}

	@Override
	public boolean shouldQueryStrategy(IStatus status) {
		CatModelSimState state = (CatModelSimState) status;

		// Don't bother querying the strategy if we are casting Rebirth/Tranquility
		if (state.mRebirthEv.isCasting() || state.mTranquilityEv.isCasting()) return false;

		// Don't bother querying the strategy if the strategy says we can wait for a while.
		if (state.mIdleTimeExpireTime > state.mSimEngine.getSimTime()) return false;

		return true;
	}

	@Override
	public SimulationTarget getActiveTarget(IStatus state) {
		return ((CatModelSimState) state).mActiveTarget;
	}
}
